﻿#region Copyright ©2008-2010, Technische Universitaet Muenchen

// ====================================================================================================
//
// Last Changed by       $Author: then $
// Last Changed Date     $LastChangedDate: 2009-05-16 14:58:56 +0200 (Sat, 16 May 2009) $
// Last Changed Revision $Rev: 144 $
//
// ====================================================================================================

#endregion

#region

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using Tum.CollabXT.TFS.Resources;

#endregion

namespace Tum.CollabXT.TFS
{
    public class WorkItems : IProcessor
    {
        private readonly List<string> IterationPathList = new List<string>();

        public void Process(IProcessProvider processProvider, TFSToolProvider toolProvider)
        {
            var workItemTemplateFileName = toolProvider.OutputPath + "/WorkItem Tracking/workitems.xml";

            #region Load template

            var workItemDoc = new XmlDocument();
            toolProvider.ProcessLog.AddEntry(Language.WorkItems_Loading);
            try
            {
                workItemDoc.Load(workItemTemplateFileName);
            }
            catch
            {
                toolProvider.ProcessLog.AddEntryAndThrowException(string.Format(Language.Error_LoadingFailed, workItemTemplateFileName));
            }

            #endregion

            toolProvider.ProcessLog.AddEntry(Language.WorkItems_SelectDefaultNode);
            var defaultWorkItemsNode = workItemDoc.SelectSingleNode("/tasks/task[@id=\"WIs\"]/taskXml/WORKITEMS");
            if (defaultWorkItemsNode == null)
                toolProvider.ProcessLog.AddEntryAndThrowException(Language.Error_NotFound_InvalidTemplate);

            #region Write milestones

            //Create hashset for decision gate types
            var decisionGateTypes = new HashSet<string>();

            //Insert some comment
            defaultWorkItemsNode.AppendChild(workItemDoc.CreateComment("Decision gates"));

            //Write
            foreach (IMilestone curMilestone in processProvider.Milestones)
            {
                try
                {
                    toolProvider.ProcessLog.AddEntry(string.Format(Language.WorkItems_WritingDecisionGate, curMilestone.Name));

                    //Create default work item node
                    var newDefWorkItemNode = workItemDoc.CreateNode("element", "WI", "");

                    //Type attribute
                    var typeAttribute = workItemDoc.CreateAttribute("type");
                    typeAttribute.Value = "Entscheidungspunkt"; //dec. gate
                    newDefWorkItemNode.Attributes.Append(typeAttribute);
                    //--------------

                    AddFieldToXmlNode(newDefWorkItemNode, "System.Title", Helper.PreapareStringForTFS(curMilestone.Name, Helper.TFS_MaxWorkItemTitleLen));
                    AddFieldToXmlNode(newDefWorkItemNode, "System.Description",
                                      Helper.PreapareStringForTFS(curMilestone.Description, Helper.TFS_MaxWorkItemTextLen));
                    AddFieldToXmlNode(newDefWorkItemNode, "System.State", toolProvider.LanguageDictionary["State_Planned"]);

                    #region Write iteration path

                    object devPhase;
                    if (curMilestone.ExtendedData.TryGetValue("DevPhase", out devPhase))
                    {
                        if (devPhase is int)
                        {
                            var iterationPathStr = string.Format("{0:D2} ", (int) devPhase);
                            switch ((int) devPhase)
                            {
                                case 0:
                                    iterationPathStr += toolProvider.LanguageDictionary["IterationPath_Initiation"];
                                    break;

                                case 99:
                                    iterationPathStr += toolProvider.LanguageDictionary["IterationPath_Closing"];
                                    break;

                                default:
                                    iterationPathStr += toolProvider.LanguageDictionary["IterationPath_Execution"];
                                    break;
                            }

                            object devIteration;
                            if (curMilestone.ExtendedData.TryGetValue("DevIteration", out devIteration))
                            {
                                if (devIteration is int)
                                    iterationPathStr += string.Format("\\" + toolProvider.LanguageDictionary["IterationPath_Iteration"] + " {0:D2}",
                                                                      (int) devIteration);
                            }

                            IterationPathList.Add(iterationPathStr);

                            iterationPathStr = "$$PROJECTNAME$$\\" + iterationPathStr;
                            curMilestone.ExtendedData["__TFS_IterationPath"] = iterationPathStr;
                            AddFieldToXmlNode(newDefWorkItemNode, "System.IterationPath", iterationPathStr);
                        }
                    }

                    #endregion

                    AddFieldToXmlNode(newDefWorkItemNode, "VMXT.id", curMilestone.Id);

                    //Decision gate type
                    object msTypeName;
                    string curDGType;
                    if(curMilestone.ExtendedData.TryGetValue("TypeName", out msTypeName))
                        curDGType = Helper.PreapareStringForTFS(msTypeName as string, Helper.TFS_MaxWorkItemTitleLen);
                    else
                        curDGType = Helper.PreapareStringForTFS(curMilestone.Name, Helper.TFS_MaxWorkItemTitleLen);

                    AddFieldToXmlNode(newDefWorkItemNode, "VMXT.Entscheidungspunkttyp", curDGType);
                    decisionGateTypes.Add(curDGType); //Add to type list

                    //Finish date
                    var finishDateStr = curMilestone.ScheduledDate.ToString("dd/MM/yy", CultureInfo.InvariantCulture);
                    curMilestone.ExtendedData["__TFS_FinishDate"] = finishDateStr;
                    AddFieldToXmlNode(newDefWorkItemNode, "Microsoft.VSTS.Scheduling.FinishDate", finishDateStr);

                    defaultWorkItemsNode.AppendChild(newDefWorkItemNode);
                }
                catch (Exception e)
                {
                    toolProvider.ProcessLog.AddEntryAndThrowException(string.Format(Language.Error_Unexpected, e.Message, e.StackTrace));
                }
            }

            #endregion

            WriteDecisionGateTypeDefinitionFile(decisionGateTypes, toolProvider.OutputPath, toolProvider.ProcessLog);


            #region Write activities

            defaultWorkItemsNode.AppendChild(workItemDoc.CreateComment("Activities"));

            foreach (IArtifact curProduct in processProvider.Artifacts)
            {
                //Check whether product is initial
                if (!curProduct.IsInitial)
                    continue;

                var belongingActivityAssociations = processProvider.GetAssociationsWithDestination<IActivity, IArtifact>(curProduct);
                var belongingMilestoneAssociations = processProvider.GetAssociationsWithSource<IArtifact, IMilestone>(curProduct);

                foreach (Association<IArtifact, IMilestone> curMilestoneLink in belongingMilestoneAssociations)
                {
                    foreach (Association<IActivity, IArtifact> curActivityLink in belongingActivityAssociations)
                    {
                        toolProvider.ProcessLog.AddEntry(string.Format(Language.WorkItems_WritingActivity, curActivityLink.Source.Name,
                                                                       curMilestoneLink.Destination.Name));

                        //Create default work item node
                        var newDefWorkItemNode = workItemDoc.CreateNode("element", "WI", string.Empty);

                        //Type attribute
                        var typeAttribute = workItemDoc.CreateAttribute("type");
                        typeAttribute.Value = "Aktivität"; //activity
                        newDefWorkItemNode.Attributes.Append(typeAttribute);
                        //--------------

                        AddFieldToXmlNode(newDefWorkItemNode, "System.Title",
                                          Helper.PreapareStringForTFS(curActivityLink.Source.Name + " (" + curMilestoneLink.Destination.Name + ")",
                                                                      Helper.TFS_MaxWorkItemTitleLen));
                        AddFieldToXmlNode(newDefWorkItemNode, "System.Description",
                                          Helper.PreapareStringForTFS(curActivityLink.Source.Description, Helper.TFS_MaxWorkItemTextLen));
                        AddFieldToXmlNode(newDefWorkItemNode, "System.State", toolProvider.LanguageDictionary["State_Planned"]);
                        AddFieldToXmlNode(newDefWorkItemNode, "VMXT.id", curActivityLink.Source.Id);

                        AddFieldToXmlNode(newDefWorkItemNode, "VMXT.EPRef", curMilestoneLink.Destination.Id);
                        AddFieldToXmlNode(newDefWorkItemNode, "VMXT.PRef", curProduct.Id);

                        #region IterationPath / FinishDate

                        //Write FinishDate/IterationPath and save them into the activity
                        object finishDate;
                        if (curMilestoneLink.Destination.ExtendedData.TryGetValue("__TFS_FinishDate", out finishDate))
                        {
                            AddFieldToXmlNode(newDefWorkItemNode, "Microsoft.VSTS.Scheduling.FinishDate", finishDate as string);
                            curActivityLink.Source.ExtendedData["__TFS_FinishDate"] = finishDate as string;
                        }

                        object iterationPath;
                        if (curMilestoneLink.Destination.ExtendedData.TryGetValue("__TFS_IterationPath", out iterationPath))
                        {
                            AddFieldToXmlNode(newDefWorkItemNode, "System.IterationPath", iterationPath as string);
                            curActivityLink.Source.ExtendedData["__TFS_IterationPath"] = iterationPath as string;
                        }

                        #endregion

                        defaultWorkItemsNode.AppendChild(newDefWorkItemNode);
                    }
                }
            }

            #endregion

            #region Write tasks

            defaultWorkItemsNode.AppendChild(workItemDoc.CreateComment("Tasks"));

            foreach (ITask curTask in processProvider.Tasks)
            {
                //Find product belonging to this task
                var taskActivityAssoc = processProvider.GetAssociationsWithSource<ITask, IActivity>(curTask);
                if (taskActivityAssoc.Length == 0)
                    continue;
                if (taskActivityAssoc.Length > 1)
                    toolProvider.ProcessLog.AddEntry("Ambiguous task-activity association.", LogEntryType.Warning);

                IActivity belongingActivity = ((Association<ITask, IActivity>) taskActivityAssoc[0]).Destination;

                var activityProductAssoc = processProvider.GetAssociationsWithSource<IActivity, IArtifact>(belongingActivity);
                if (activityProductAssoc.Length == 0)
                    continue;
                if (activityProductAssoc.Length > 1)
                    toolProvider.ProcessLog.AddEntry("Ambiguous activity-artifact association.", LogEntryType.Warning);

                IArtifact belongingProduct = ((Association<IActivity, IArtifact>)activityProductAssoc[0]).Destination;
                //--------------------------------------

                //Only write initial tasks
                if (!belongingProduct.IsInitial)
                    continue;


                toolProvider.ProcessLog.AddEntry(string.Format(Language.WorkItems_WritingTask, curTask.Name));

                //Create default work item node
                var newDefWorkItemNode = workItemDoc.CreateNode("element", "WI", string.Empty);

                //Type attribute
                var typeAttribute = workItemDoc.CreateAttribute("type");
                typeAttribute.Value = "Arbeitsauftrag"; //task
                newDefWorkItemNode.Attributes.Append(typeAttribute);
                //--------------

                AddFieldToXmlNode(newDefWorkItemNode, "System.Title", Helper.PreapareStringForTFS(curTask.Name, Helper.TFS_MaxWorkItemTitleLen));
                AddFieldToXmlNode(newDefWorkItemNode, "System.Description", Helper.PreapareStringForTFS(curTask.Description, Helper.TFS_MaxWorkItemTextLen));

                AddFieldToXmlNode(newDefWorkItemNode, "VMXT.ARef", belongingActivity.Id);


                #region Discipline

                //Find and write discipline
                var productDisciplineAssoc = processProvider.GetAssociationsWithSource<IArtifact, IDiscipline>(belongingProduct);
                if (productDisciplineAssoc.Length == 0)
                    toolProvider.ProcessLog.AddEntry("Product without discipline.", LogEntryType.Warning);
                else
                {
                    if (productDisciplineAssoc.Length > 1)
                        toolProvider.ProcessLog.AddEntry("Ambiguous product-discipline association.", LogEntryType.Warning);

                    AddFieldToXmlNode(newDefWorkItemNode, "VMXT.Discipline", ((Association<IArtifact, IDiscipline>)productDisciplineAssoc[0]).Destination.Name);
                }

                #endregion

                #region IterationPath / FinishDate

                object finishDate;
                if (belongingActivity.ExtendedData.TryGetValue("__TFS_FinishDate", out finishDate))
                    AddFieldToXmlNode(newDefWorkItemNode, "Microsoft.VSTS.Scheduling.FinishDate", finishDate as string);

                object iterationPath;
                if(belongingActivity.ExtendedData.TryGetValue("__TFS_IterationPath", out iterationPath))
                    AddFieldToXmlNode(newDefWorkItemNode, "System.IterationPath", iterationPath as string);

                #endregion

                defaultWorkItemsNode.AppendChild(newDefWorkItemNode);
            }

            #endregion

            WriteTaskTypeDefinitionFile(processProvider.Disciplines, toolProvider.OutputPath, toolProvider.ProcessLog);

            #region Write initial products

            //Insert some comment
            defaultWorkItemsNode.AppendChild(workItemDoc.CreateComment("Initial products"));

            //Write
            foreach (IArtifact curProduct in processProvider.Artifacts)
            {
                if (curProduct.IsInitial)
                {
                    try
                    {
                        toolProvider.ProcessLog.AddEntry(string.Format(Language.WorkItems_WritingProduct, curProduct.Name));

                        //Create default work item node
                        var newDefWorkItemNode = workItemDoc.CreateNode("element", "WI", string.Empty);

                        //Type attribute
                        var typeAttribute = workItemDoc.CreateAttribute("type");
                        typeAttribute.Value = "Produkt"; //product
                        newDefWorkItemNode.Attributes.Append(typeAttribute);
                        //--------------

                        AddFieldToXmlNode(newDefWorkItemNode, "System.Title", Helper.PreapareStringForTFS(curProduct.Name, Helper.TFS_MaxWorkItemTitleLen));
                        AddFieldToXmlNode(newDefWorkItemNode, "System.Description",
                                          Helper.PreapareStringForTFS(curProduct.Description, Helper.TFS_MaxWorkItemTextLen));
                        AddFieldToXmlNode(newDefWorkItemNode, "VMXT.id", curProduct.Id);
                        AddFieldToXmlNode(newDefWorkItemNode, "VMXT.Erzeugung", toolProvider.LanguageDictionary["Creation_Initial"]);
                        AddFieldToXmlNode(newDefWorkItemNode, "VMXT.ProduktTyp",
                                          Helper.PreapareStringForTFS(curProduct.TypeName, Helper.TFS_MaxWorkItemTitleLen));

                        defaultWorkItemsNode.AppendChild(newDefWorkItemNode);
                    }
                    catch (Exception e)
                    {
                        toolProvider.ProcessLog.AddEntryAndThrowException(string.Format(Language.Error_Unexpected, e.Message, e.StackTrace));
                    }
                }
            }

            #endregion

            WriteProductTypeDefinitionFile(processProvider.Artifacts, toolProvider.OutputPath, toolProvider.ProcessLog);

            #region Save work items file

            toolProvider.ProcessLog.AddEntry(Language.WorkItems_Saving);
            try
            {
                workItemDoc.Save(workItemTemplateFileName);
            }
            catch
            {
                toolProvider.ProcessLog.AddEntryAndThrowException(string.Format(Language.Error_SavingFailed, workItemTemplateFileName));
            }

            #endregion
        }

        private static void AddFieldToXmlNode(XmlNode node, string fieldName, string fieldValue)
        {
            var subNodeTitle = node.OwnerDocument.CreateNode("element", "FIELD", "");
            var refnameAttribute = node.OwnerDocument.CreateAttribute("refname");
            refnameAttribute.Value = fieldName;
            subNodeTitle.Attributes.Append(refnameAttribute);
            var valueAttribute = node.OwnerDocument.CreateAttribute("value");
            valueAttribute.Value = fieldValue;
            subNodeTitle.Attributes.Append(valueAttribute);
            node.AppendChild(subNodeTitle);
        }


        private void WriteDecisionGateTypeDefinitionFile(HashSet<string> foundDGTypes, string outputPath, Log log)
        {
            var typeDefinitionFileName = outputPath + "/WorkItem Tracking/TypeDefinitions/VMXT_Entscheidungspunkt.xml";

            #region Load template

            var typeDefinitionDoc = new XmlDocument();
            log.AddEntry(Language.DecisionGateTypes_Loading);
            try
            {
                typeDefinitionDoc.Load(typeDefinitionFileName);
            }
            catch
            {
                log.AddEntryAndThrowException(string.Format(Language.Error_LoadingFailed, typeDefinitionFileName));
            }

            #endregion

            log.AddEntry(Language.DecisionGateTypes_SelectingTypesNode);
            var dgTypesNode = typeDefinitionDoc.SelectSingleNode("/WITD/WORKITEMTYPE/FIELDS/FIELD[@refname='VMXT.Entscheidungspunkttyp']/ALLOWEDVALUES");
            if (dgTypesNode == null)
                log.AddEntryAndThrowException(Language.Error_NotFound_InvalidTemplate);

            foreach (string curValue in foundDGTypes)
            {
                log.AddEntry(string.Format(Language.DecisionGateTypes_WritingType, curValue));

                var newTypeNode = typeDefinitionDoc.CreateNode("element", "LISTITEM", "");
                var valueAttribute = typeDefinitionDoc.CreateAttribute("value");
                valueAttribute.Value = curValue;
                newTypeNode.Attributes.Append(valueAttribute);
                dgTypesNode.AppendChild(newTypeNode);
            }

            #region Save type definition file

            log.AddEntry(Language.DecisionGateTypes_Saving);
            try
            {
                typeDefinitionDoc.Save(typeDefinitionFileName);
            }
            catch
            {
                log.AddEntryAndThrowException(string.Format(Language.Error_SavingFailed, typeDefinitionFileName));
            }

            #endregion
        }

        private static void WriteTaskTypeDefinitionFile(IEnumerable<IDiscipline> disciplineList, string outputPath, Log log)
        {
            var typeDefinitionFileName = outputPath + "/WorkItem Tracking/TypeDefinitions/VMXT_Arbeitsauftrag.xml";

            #region Load template

            var typeDefinitionDoc = new XmlDocument();
            log.AddEntry(Language.TaskTypes_Loading);
            try
            {
                typeDefinitionDoc.Load(typeDefinitionFileName);
            }
            catch
            {
                log.AddEntryAndThrowException(string.Format(Language.Groups_Loading, typeDefinitionFileName));
            }

            #endregion

            log.AddEntry(Language.TaskTypes_SelectingDisciplineNodes);
            var disciplinesNode = typeDefinitionDoc.SelectSingleNode("/WITD/WORKITEMTYPE/FIELDS/FIELD[@refname='VMXT.Discipline']/ALLOWEDVALUES");
            if (disciplinesNode == null)
                log.AddEntryAndThrowException(Language.Error_NotFound_InvalidTemplate);

            var alreadyWrittenDisciplines = new HashSet<string>();

            foreach (IDiscipline curValue in disciplineList)
            {
                if (alreadyWrittenDisciplines.Add(curValue.Name))
                {
                    log.AddEntry(string.Format(Language.TaskTypes_WritingDiscipline, curValue.Name));

                    var newTypeNode = typeDefinitionDoc.CreateNode("element", "LISTITEM", "");
                    var valueAttribute = typeDefinitionDoc.CreateAttribute("value");
                    valueAttribute.Value = curValue.Name;
                    newTypeNode.Attributes.Append(valueAttribute);
                    disciplinesNode.AppendChild(newTypeNode);
                }
            }

            #region Save type definition file

            log.AddEntry(Language.TaskTypes_Saving);
            try
            {
                typeDefinitionDoc.Save(typeDefinitionFileName);
            }
            catch
            {
                log.AddEntryAndThrowException(string.Format(Language.Error_SavingFailed, typeDefinitionFileName));
            }

            #endregion
        }

        private static void WriteProductTypeDefinitionFile(IEnumerable<IArtifact> productList, string outputPath, Log log)
        {
            var typeDefinitionFileName = outputPath + "/WorkItem Tracking/TypeDefinitions/VMXT_Produkt.xml";

            #region Load template

            var typeDefinitionDoc = new XmlDocument();
            log.AddEntry(Language.ProductTypes_Loading);
            try
            {
                typeDefinitionDoc.Load(typeDefinitionFileName);
            }
            catch
            {
                log.AddEntryAndThrowException(string.Format(Language.Error_LoadingFailed, typeDefinitionFileName));
            }

            #endregion

            log.AddEntry(Language.ProductTypes_SelectingTypesNode);
            var productTypesNode = typeDefinitionDoc.SelectSingleNode("/WITD/WORKITEMTYPE/FIELDS/FIELD[@refname='VMXT.ProduktTyp']/ALLOWEDVALUES");
            if (productTypesNode == null)
                log.AddEntryAndThrowException(Language.Error_NotFound_InvalidTemplate);

            var alreadyWrittenProducts = new HashSet<string>();

            foreach (IArtifact curValue in productList)
            {
                if (alreadyWrittenProducts.Add(curValue.Name))
                {
                    log.AddEntry(string.Format(Language.ProductTypes_WritingType, curValue.Name));

                    var newTypeNode = typeDefinitionDoc.CreateNode("element", "LISTITEM", "");
                    var valueAttribute = typeDefinitionDoc.CreateAttribute("value");
                    valueAttribute.Value = curValue.Name;
                    newTypeNode.Attributes.Append(valueAttribute);
                    productTypesNode.AppendChild(newTypeNode);
                }
            }

            #region Save type definition file

            log.AddEntry(Language.ProductTypes_Saving);
            try
            {
                typeDefinitionDoc.Save(typeDefinitionFileName);
            }
            catch
            {
                log.AddEntryAndThrowException(string.Format(Language.Error_SavingFailed, typeDefinitionFileName));
            }

            #endregion
        }


        public string[] GetIterationPaths()
        {
            return IterationPathList.ToArray();
        }
    }
}